package jmine.tec.script.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.annotations.Unmodifiable;
import jmine.tec.script.ScriptExecutionContext;
import jmine.tec.script.ScriptExecutor;
import jmine.tec.script.ScriptExecutorException;

/**
 * Implementação de script dispatcher que trata diferentemente executores stateful de executores stateless (anotados com @Unmodifiable). Os
 * executores stateful são colocados em uma fila e utilizados por 1 thread por vez, enquanto os executores stateless serão utilizados
 * concorrentemente.
 * 
 * @author lundberg
 */
public class ConcurrentScriptDispatcher extends QueuedScriptDispatcher {

    private Map<Class<? extends ScriptExecutor>, ScriptExecutor> unmodifiableExecutors =
            new HashMap<Class<? extends ScriptExecutor>, ScriptExecutor>();

    /**
     * Executa o contexto dado.
     * 
     * @param <R> O tipo do resultado
     * @param context o contexto de execução
     * @return R
     * @throws ScriptExecutorException Se um erro ocorrer no dispatch
     */
    @Override
    public <R> R dispatchExecution(final ScriptExecutionContext<R> context) throws ScriptExecutorException {
        if (this.hasUnmodifiableExecutor(context)) {
            return this.findExecutor(context).execute(context);
        } else {
            return super.dispatchExecution(context);
        }
    }

    /**
     * Devolve as dependêcias do contexto dado.
     * 
     * @param <R> O tipo do resultado
     * @param context o contexto de execução
     * @return R
     * @throws ScriptExecutorException Se um erro ocorrer no dispatch
     */
    @Override
    public <R> R dispatchGetDependencies(final ScriptExecutionContext<R> context) throws ScriptExecutorException {
        if (this.hasUnmodifiableExecutor(context)) {
            return this.findExecutor(context).getDependencies(context);
        } else {
            return super.dispatchGetDependencies(context);
        }
    }

    /**
     * Devovle os erros de validação para o contexto dado.
     * 
     * @param <R> O tipo do resultado
     * @param context o contexto de execução
     * @return R
     * @throws ScriptExecutorException Se um erro ocorrer no dispatch
     */
    @Override
    public <R> R dispatchGetValidations(ScriptExecutionContext<R> context) throws ScriptExecutorException {
        if (this.hasUnmodifiableExecutor(context)) {
            return this.findExecutor(context).getValidations(context);
        } else {
            return super.dispatchGetValidations(context);
        }
    }

    /**
     * Verifica se o dispatcher dispôe de um executor thread safe para o contexto.
     * 
     * @param context context
     * @return boolean
     */
    private boolean hasUnmodifiableExecutor(ScriptExecutionContext<?> context) {
        return this.unmodifiableExecutors.containsKey(context.getScriptExecutorClass());
    }

    /**
     * Encontra a instância de executor de script apropriada para o contexto.
     * 
     * @param context context
     * @return ScriptExecutor
     */
    private ScriptExecutor findExecutor(ScriptExecutionContext<?> context) {
        return this.unmodifiableExecutors.get(context.getScriptExecutorClass());
    }

    /**
     * Registra este Executor.
     * 
     * @param exec O executor que deve ser usado neste dispatcher
     */
    @Override
    public void setScriptExecutors(final ScriptExecutor... exec) {
        List<ScriptExecutor> unsafeExecutors = new ArrayList<ScriptExecutor>();
        for (ScriptExecutor executor : exec) {
            if (executor.getClass().isAnnotationPresent(Unmodifiable.class)) {
                this.unmodifiableExecutors.put(this.getExecutorKey(executor), executor);
            } else {
                unsafeExecutors.add(executor);
            }
        }
        super.setScriptExecutors(unsafeExecutors.toArray(new ScriptExecutor[unsafeExecutors.size()]));
    }

}
